/**
 * StarNode.java
 *
 * Roslan's Remake of MOO in 3D using Ardor3D
 * (C) 2011 Roslan Amir. All rights reserved.
 */

package com.roslan.games.moo3d.spatial;

import java.awt.Point;

import com.ardor3d.bounding.BoundingBox;
import com.ardor3d.image.Texture;
import com.ardor3d.math.Vector3;
import com.ardor3d.math.type.ReadOnlyColorRGBA;
import com.ardor3d.renderer.state.MaterialState;
import com.ardor3d.renderer.state.MaterialState.ColorMaterial;
import com.ardor3d.renderer.state.TextureState;
import com.ardor3d.scenegraph.Mesh;
import com.ardor3d.scenegraph.Node;
import com.ardor3d.scenegraph.Spatial;
import com.ardor3d.scenegraph.hint.DataMode;
import com.ardor3d.scenegraph.shape.Sphere;
import com.ardor3d.scenegraph.shape.Sphere.TextureMode;
import com.ardor3d.ui.text.BMText;
import com.ardor3d.ui.text.BMText.AutoFade;
import com.ardor3d.ui.text.BMText.AutoScale;
import com.ardor3d.util.TextureManager;
import com.roslan.games.moo3d.GameData;
import com.roslan.games.moo3d.data.Colony;
import com.roslan.games.moo3d.data.Race;
import com.roslan.games.moo3d.data.Star;
import com.roslan.games.moo3d.utils.BMFontLoader;
import com.roslan.games.moo3d.utils.HexGridUtils;

/**
 * A <code>StarNode</code> is a <code>Node</code> representing a star displayed on the galaxy map.
 * 
 * @author Roslan Amir
 * @version 1.0 - Jan 7, 2011
 */
public class StarNode extends Node {

	/**
	 * The radius of the Sphere used to represent a star.
	 */
	public static final double STAR_SPHERE_RADIUS = 0.5;

	/**
	 * The texture to use to indicate a colonized star.
	 */
	private static final String IMAGE_COLONY_TEXTURE = "images/earth_640_320.jpg"; 

	/**
	 * The Mesh to represent an uncolonized star.
	 */
	private static final Sphere baseStarMesh;

	/**
	 * The Mesh to represent a colonized star.
	 */
	private static final Sphere baseColonyMesh;

	/**
	 * 
	 */
	private static final double FONT_SCALE = 0.95;

	/*
	 * Create the Sphere object representing each colored star.
	 */
	static {
		/*
		 * Create colored spheres as the model for the various colored stars. Also used as the Mesh for Orion.
		 */
		baseStarMesh = new Sphere("StarSphere", Vector3.ZERO, 64, 64, STAR_SPHERE_RADIUS, TextureMode.Linear);
		baseStarMesh.setModelBound(new BoundingBox());
		baseStarMesh.getSceneHints().setDataMode(DataMode.VBO);
		baseStarMesh.updateModelBound();

		/*
		 * Create a textured sphere as the model for the colonies. Also used as the Mesh for the player's home colony.
		 */
		baseColonyMesh = (Sphere) baseStarMesh.makeCopy(true);
		baseColonyMesh.setName("ColonySphere");
		baseColonyMesh.updateModelBound();

		final TextureState textureState = new TextureState();
		textureState.setTexture(TextureManager.load(IMAGE_COLONY_TEXTURE, Texture.MinificationFilter.Trilinear, true));
		baseColonyMesh.setRenderState(textureState);

		final MaterialState materialState = new MaterialState();
		materialState.setColorMaterial(ColorMaterial.Emissive);
		baseColonyMesh.setRenderState(materialState);
	}

	/**
	 * The indexes for the tile where this star is in the 2-dimensional array representing the galaxy map.
	 */
	public Point tile;

	/**
	 * The range (in parsecs) of this star/colony from the nearest player's colony.
	 */
	public int range;

	/**
	 * Creates an instance of the <code>StarNode</code> class.
	 * 
	 * @param name - the name of the star
	 * @param x - the x index of the star in the galaxy map
	 * @param y - the y index of the star in the galaxy map
	 */
	private StarNode(String name, int x, int y) {
		super(name);
		this.tile = new Point(x, y);
		this.range = Integer.MAX_VALUE;
	}

	/**
	 * Return the <code>Star</code> instance embedded within this <code>StarNode</code>.
	 *
	 * @return the <code>Star</code> instance embedded within this <code>StarNode</code>
	 */
	public Star getStar() {
		return (Star) this.getUserData();
	}

	/**
	 * Sets the range (in parsecs) of this star from the given player's colony.
	 * This new range is set if it is less than the current range.
	 * 
	 * @param fromColony - the player's colony from where to calculate the range
	 */
	public void setRange(StarNode fromColony) {
		int dist = HexGridUtils.distance(fromColony.tile, this.tile);
		if (dist < this.range) {
			this.range = dist;
		}
	}

	/**
	 * Add the name of the star to this node. This is done is for explored star.
	 *
	 * @param textColor - the color of the text (based on the race color)
	 */
	public void addStarNameText(ReadOnlyColorRGBA textColor) {
		final BMText text = new BMText(this.getName(), this.getName(), BMFontLoader.getDefaultFont(), BMText.Align.Center, BMText.Justify.Center);
		text.setFontScale(FONT_SCALE);
//		text.setSolidColor(null);
		text.setDefaultColor(textColor);
//		text.setAutoFadeDistanceRange(nearOpaque, farTransparent);
		text.setAutoFade(AutoFade.Off);
		text.setAutoScale(AutoScale.FixedScreenSize);
		text.setAutoRotate(true);
		text.setTranslation(0, -1.50, 0);
		this.attachChild(text);
	}

	/**
	 * Add a new star billboard to this node. This is done is for explored star.
	 */
	public void addStarBillboard() {
		StarBannerBillboard billboard = new StarBannerBillboard(this.getName());
		billboard.setTranslation(0, -2.00, 0);
		this.attachChild(billboard);
	}

	/**
	 * Convert this Star to a Colony for the specified Race.
	 * 
	 * @param race - the race colonizing this Star.
	 */
	public void colonize(Race race) {
		/*
		 * Create a new Colony object based on the current Star object and add the new Colony to this StarNode.
		 */
		Star star = this.getStar();
		Colony colony = Colony.createNewColony(star, race);
		this.setUserData(colony);

		/*
		 * Replace the Star Mesh object with the Colony Mesh object.
		 */
		Spatial oldMesh = this.detachChildAt(0);
		Mesh newMesh = baseColonyMesh.makeCopy(true);
		newMesh.setName(oldMesh.getName());
		this.attachChildAt(newMesh, 0);

		/*
		 * Add the Colony name to this star node.
		 */
		this.addStarNameText(GameData.BANNER_COLORS[race.bannerIndex]);
		this.addStarBillboard();

		/*
		 * Add the scanner circle around this colony.
		 */
		final ScannerCircle circle = new ScannerCircle(race.scannerRange, GameData.BANNER_COLORS[race.bannerIndex]);
		this.attachChild(circle);

		/*
		 * Update the range for other stars accordingly.
		 */
		this.range = 0;
		for (StarNode starNode : GameData.getInstance().starList) {
			if (starNode != this)
				starNode.setRange(this);
		}
	}

	/**
	 * Create the <code>StarNode</code> object representing the Orion star system.
	 * 
	 * @param x - the x index of the star in the galaxy map
	 * @param y - the y index of the star in the galaxy map
	 * @return the <code>StarNode</code> object representing the Orion star system
	 */
	public static StarNode createOrionNode(int x, int y) {
		final Star orion = Star.createOrion();

		final StarNode orionNode = new StarNode(orion.name, x, y);
		final double[] origin = HexGridUtils.getHexWorldOrigin(x, y, null);
		orionNode.setTranslation(origin[0], origin[1], 0);
		orionNode.setUserData(orion);

//		final Mesh mesh = starMesh.makeCopy(true);
//		mesh.updateModelBound();
		baseStarMesh.setName(orion.name);
		orionNode.attachChild(baseStarMesh);

		final MaterialState renderState = new MaterialState();
		renderState.setAmbient(GameData.STAR_COLORS_DARK[orion.colorIndex]);
		baseStarMesh.setRenderState(renderState);

		return orionNode;
	}

	/**
	 * Create the <code>StarNode</code> object representing the player's home star system.
	 * 
	 * @param x - the x index of the star in the galaxy map
	 * @param y - the y index of the star in the galaxy map
	 * @return the <code>StarNode</code> object representing the player's home star system
	 */
	public static StarNode createPlayerHomeStarNode(int x, int y) {
		final GameData gameData = GameData.getInstance();

		final Colony playerHomeWorld = Colony.createHomeWorld(gameData.playerRace, gameData.difficultyIndex <= 2); // 2 = AVERAGE
		playerHomeWorld.name = gameData.homeStarName;
		playerHomeWorld.explored = true;

		final StarNode starNode = new StarNode(gameData.homeStarName, x, y);
		final double[] origin = HexGridUtils.getHexWorldOrigin(x, y, null);
		starNode.setTranslation(origin[0], origin[1], 0);
		starNode.setUserData(playerHomeWorld);

		/*
		 * Update the range of Orion relative to the player home star.
		 */
		StarNode orionNode = gameData.starList.get(Star.STAR_NAMES[0]);
		orionNode.setRange(starNode);

		baseColonyMesh.setName(gameData.homeStarName);
		starNode.attachChild(baseColonyMesh);

		/*
		 * Add the Colony name to this star node.
		 */
		starNode.addStarNameText(GameData.BANNER_COLORS[gameData.playerBannerIndex]);
		starNode.addStarBillboard();

		/*
		 * Add the scanner circle around this colony.
		 */
		final ScannerCircle circle = new ScannerCircle(gameData.playerRace.scannerRange, GameData.BANNER_COLORS[gameData.playerBannerIndex]);
		starNode.attachChild(circle);

		return starNode;
	}

	/**
	 * Create the <code>StarNode</code> object representing the AI player's home star system.
	 * 
	 * @param x - the x index of the star in the galaxy map
	 * @param y - the y index of the star in the galaxy map
	 * @param race - the owner race of the star
	 * @return the <code>StarNode</code> object representing the AI player's home star system
	 */
	public static StarNode createAiHomeStarNode(int x, int y, Race race) {
		final GameData gameData = GameData.getInstance();

		final Colony aiHomeWorld = Colony.createHomeWorld(race, gameData.difficultyIndex <= 2); // 2 = AVERAGE

		final StarNode starNode = new StarNode(aiHomeWorld.name, x, y);
		final double[] origin = HexGridUtils.getHexWorldOrigin(x, y, null);
		starNode.setTranslation(origin[0], origin[1], 0);
		starNode.setRange(gameData.playerHomeStarNode);
		starNode.setUserData(aiHomeWorld);

		final Mesh mesh = baseStarMesh.makeCopy(true);
		mesh.updateModelBound();
		mesh.setName(aiHomeWorld.name);
		starNode.attachChild(mesh);

		final MaterialState renderState = new MaterialState();
		renderState.setAmbient(GameData.STAR_COLORS_DARK[aiHomeWorld.colorIndex]);
		mesh.setRenderState(renderState);

		return starNode;
	}

	/**
	 * Create the <code>StarNode</code> object representing a random star system.
	 *
	 * @param x - the x index of the star in the galaxy map
	 * @param y - the y index of the star in the galaxy map
	 * @return the <code>StarNode</code> object representing the random star system
	 */
	public static StarNode createRandomStarNode(int x, int y) {
		final GameData gameData = GameData.getInstance();

		final Star star = Star.createRandomStar();

		final StarNode starNode = new StarNode(star.name, x, y); // Star.STAR_NAMES[0] = "Orion"
		final double[] origin = HexGridUtils.getHexWorldOrigin(x, y, null);
		starNode.setTranslation(origin[0], origin[1], 0);
		starNode.setUserData(star);
		starNode.setRange(gameData.playerHomeStarNode);

		final Mesh mesh = baseStarMesh.makeCopy(true);
		mesh.updateModelBound();
		mesh.setName(star.name);
		starNode.attachChild(mesh);

		ReadOnlyColorRGBA  color = (starNode.range <= gameData.playerRace.scannerRange) ? GameData.STAR_COLORS_BRIGHT[star.colorIndex] : GameData.STAR_COLORS_DARK[star.colorIndex];
		final MaterialState renderState = new MaterialState();
		renderState.setAmbient(color);
		mesh.setRenderState(renderState);

		return starNode;
	}

	/*
	 * @see java.lang.Object#hashCode()
	 */
	@Override
	public int hashCode() {
		return this.getName().hashCode();
	}

	/*
	 * @see java.lang.Object#equals(java.lang.Object)
	 */
	@Override
	public boolean equals(Object obj) {
		if (!(obj instanceof StarNode))
			return false;
		if (this == obj)
			return true;
		return this.getName().equals(((StarNode) obj).getName());
	}

	/*
	 * @see java.lang.Object#toString()
	 */
	@Override
	public String toString() {
		return "StarNode [name=" + this.getName() + ", tile=(" + tile.x + ',' + tile.y + "), range=" + range + ']';
	}

}
